package com.guyu.plugin.satoken.helper;

import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.SaTerminalInfo;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaTokenConsts;
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;


@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class LoginHelper {

    private static final Map<String, StpLogic> STP_LOGIC_MAP = new ConcurrentHashMap<>();

    /**
     * 获取逻辑实现类
     *
     * @param loginType 账号类型标识
     */
    public static StpLogic getStpLogic(String loginType) {
        return STP_LOGIC_MAP.get(loginType);
    }

    /**
     * 注册逻辑实现类
     *
     * @param loginType 账号类型标识
     * @param config    配置对象
     */
    public static StpLogic register(String loginType, SaTokenConfig config) {
        StpLogic stpLogic = new StpLogic(loginType);
        stpLogic.setConfig(config);
        STP_LOGIC_MAP.put(loginType, stpLogic);
        return stpLogic;
    }

    /**
     * 卸载逻辑实现类
     *
     * @param loginType 账号类型标识
     */
    public static void unregister(String loginType) {
        STP_LOGIC_MAP.remove(loginType);
    }

    /**
     * 会话登录
     *
     * @param loginType 账号类型标识
     * @param loginId   账号ID
     */
    public static StpLogic login(String loginType, long loginId) {
        return login(loginType, loginId, null);
    }

    /**
     * 会话登录
     *
     * @param loginType  账号类型标识
     * @param loginId    账号ID
     * @param clientType 客户端类型
     * @param clientId   客户端ID
     */
    public static StpLogic login(String loginType, long loginId, String clientType, String clientId) {
        return login(loginType, loginId, SaLoginParameter.create().setDeviceType(clientType).setDeviceId(clientId));
    }

    /**
     * 会话登录
     *
     * @param loginType      账号类型标识
     * @param loginId        账号ID
     * @param loginParameter 此次登录的参数Model
     */
    public static StpLogic login(String loginType, long loginId, SaLoginParameter loginParameter) {
        StpLogic stpLogic = STP_LOGIC_MAP.get(loginType);
        stpLogic.login(loginId, loginParameter);
        return stpLogic;
    }

    /**
     * 注销 (根据 loginId)
     *
     * @param loginType 账号类型标识
     * @param loginId   账号ID
     */
    public static void logout(String loginType, long loginId) {
        STP_LOGIC_MAP.get(loginType).logout(loginId);
    }

    /**
     * 会话登录
     *
     * @param loginType      账号类型标识
     * @param loginId        账号ID
     * @param loginParameter 此次登录的参数Model
     * @param userInfo       登录用户信息
     */
    public static StpLogic loginAndPutUserInfoToSession(String loginType, long loginId, SaLoginParameter loginParameter, Object userInfo) {
        StpLogic stpLogic = login(loginType, loginId, loginParameter);
        putUserInfoToSession(stpLogic, userInfo);
        return stpLogic;
    }

    /**
     * 会话登录
     *
     * @param loginType      账号类型标识
     * @param loginId        账号ID
     * @param loginParameter 此次登录的参数Model
     * @param userInfo       登录用户信息
     */
    public static StpLogic loginAndPutUserInfoToTokenSession(String loginType, long loginId, SaLoginParameter loginParameter, Object userInfo) {
        StpLogic stpLogic = login(loginType, loginId, loginParameter);
        putUserInfoToTokenSession(stpLogic, userInfo);
        return stpLogic;
    }

    /**
     * 获取当前已登录账号的 Account-Session
     *
     * @param loginType 账号类型标识
     */
    public static SaSession getSession(String loginType) {
        return STP_LOGIC_MAP.get(loginType).getSession();
    }

    /**
     * 获取指定账号 id 的 Account-Session
     *
     * @param loginType 账号类型标识
     * @param loginId   账号ID
     */
    public static SaSession getSessionByLoginId(String loginType, long loginId) {
        return STP_LOGIC_MAP.get(loginType).getSessionByLoginId(loginId);
    }

    /**
     * 获取当前 token 的 Token-Session
     *
     * @param loginType 账号类型标识
     */
    public static SaSession getTokenSession(String loginType) {
        return STP_LOGIC_MAP.get(loginType).getTokenSession();
    }

    /**
     * 获取指定 token 的 Token-Session
     *
     * @param loginType 账号类型标识
     */
    public static SaSession getTokenSessionByToken(String loginType, String token) {
        return STP_LOGIC_MAP.get(loginType).getTokenSessionByToken(token);
    }

    /**
     * 获取请求token
     */
    public static String getTokenValue(HttpServletRequest request, StpLogic stpLogic) {
        // 1、获取token名称
        String tokenName = stpLogic.getTokenName();
        // 2、根据token名称获取token值
        String tokenValue = request.getHeader(tokenName);
        // 3、如果配置打开了前缀模式，则二次处理一下
        String tokenPrefix = stpLogic.getConfigOrGlobal().getTokenPrefix();
        if (SaFoxUtil.isNotEmpty(tokenPrefix)) {
            // 情况3.1：如果提交的 token 为空，则转为 null
            if (SaFoxUtil.isEmpty(tokenValue)) {
                tokenValue = null;
            }

            // 情况3.2：如果 token 有值，但是并不是以指定的前缀开头
            else if (!tokenValue.startsWith(tokenPrefix + SaTokenConsts.TOKEN_CONNECTOR_CHAT)) {
                tokenValue = null;
            } else {
                // 情况3.3：代码至此，说明 token 有值，且是以指定的前缀开头的，现在裁剪掉前缀
                tokenValue = tokenValue.substring(tokenPrefix.length() + SaTokenConsts.TOKEN_CONNECTOR_CHAT.length());
            }
        }
        // 4、返回
        return tokenValue;
    }


    /**
     * 获取请求token
     */
    public static String getTokenValue(HttpServletRequest request, String loginType) {
        StpLogic stpLogic = STP_LOGIC_MAP.get(loginType);
        return getTokenValue(request, stpLogic);
    }

    /**
     * 设置用户信息到登录会话
     *
     * @param saSession 会话作用域
     * @param userInfo  登录用户信息
     */
    public static void putUserInfo(SaSession saSession, Object userInfo) {
        saSession.set(SaSession.USER, JSON.toJSONString(userInfo));
    }

    /**
     * 设置用户信息到Account-Session
     *
     * @param stpLogic 逻辑实现类
     * @param userInfo 登录用户信息
     */
    public static void putUserInfoToSession(StpLogic stpLogic, Object userInfo) {
        putUserInfo(stpLogic.getSession(), userInfo);
    }

    /**
     * 设置用户信息到Token-Session
     *
     * @param stpLogic 逻辑实现类
     * @param userInfo 登录用户信息
     */
    public static void putUserInfoToTokenSession(StpLogic stpLogic, Object userInfo) {
        putUserInfo(stpLogic.getTokenSession(), userInfo);
    }

    /**
     * 获取登录用户信息
     */
    public static <T> T getUserInfo(SaSession saSession, Class<T> clazz) {
        if (Objects.isNull(saSession)) {
            return null;
        }
        Object userInfo = saSession.get(SaSession.USER);
        if (Objects.isNull(userInfo)) {
            return null;
        }
        return JSON.parseObject(userInfo.toString(), clazz);
    }

    /**
     * 从 Account-Session 中获取登录用户信息
     */
    public static <T> T getUserInfoBySession(String loginType, Class<T> clazz) {
        SaSession session = getSession(loginType);
        return getUserInfo(session, clazz);
    }

    /**
     * 从 Token-Session 中获取登录用户信息
     */
    public static <T> T getUserInfoByTokenSession(String loginType, Class<T> clazz) {
        SaSession session = getTokenSession(loginType);
        return getUserInfo(session, clazz);
    }

    /**
     * 从 Account-Session 中获取指定用户信息
     */
    public static <T> T getUserInfoByLoginId(String loginType, long loginId, Class<T> clazz) {
        SaSession saSession = getSessionByLoginId(loginType, loginId);
        return LoginHelper.getUserInfo(saSession, clazz);
    }


    /**
     * 从 Token-Session 中获取指定用户信息
     */
    public static <T> T getUserInfoByToken(String loginType, String token, Class<T> clazz) {
        SaSession session = getTokenSessionByToken(loginType, token);
        return getUserInfo(session, clazz);
    }

    /**
     * 从请求中获取登录用户信息
     */
    public static <T> T getUserInfoByRequestForSession(HttpServletRequest request, String loginType, Class<T> clazz) {
        StpLogic stpLogic = STP_LOGIC_MAP.get(loginType);
        String tokenValue = getTokenValue(request, stpLogic);
        Object loginId = stpLogic.getLoginIdByTokenNotThinkFreeze(tokenValue);
        SaSession session = stpLogic.getSessionByLoginId(loginId);
        return getUserInfo(session, clazz);
    }

    /**
     * 从请求中获取登录用户信息
     */
    public static <T> T getUserInfoByRequestForTokenSession(HttpServletRequest request, String loginType, Class<T> clazz) {
        StpLogic stpLogic = STP_LOGIC_MAP.get(loginType);
        String tokenValue = getTokenValue(request, stpLogic);
        SaSession session = stpLogic.getTokenSessionByToken(tokenValue);
        return getUserInfo(session, clazz);
    }

    /**
     * 获取指定 token 对应的账号id，如果 token 无效或 token 处于被踢、被顶等状态 (不考虑被冻结)，则返回 null
     *
     * @param tokenValue token
     * @return 账号id
     */
    public static Object getLoginIdByTokenNotThinkFreeze(String loginType, String tokenValue) {
        StpLogic stpLogic = STP_LOGIC_MAP.get(loginType);
        return stpLogic.getLoginIdByTokenNotThinkFreeze(tokenValue);
    }

    /**
     * 获取指定账号 id 已登录设备信息集
     *
     * @param loginType 账号类型
     * @param loginId   账号ID
     */
    public static List<SaTerminalInfo> getTerminalListByLoginId(String loginType, long loginId) {
        return STP_LOGIC_MAP.get(loginType).getTerminalListByLoginId(loginId);
    }

    /**
     * 将会话账号id转换为 Long 类型
     */
    public static long getLoginIdAsString(String loginType) {
        return STP_LOGIC_MAP.get(loginType).getLoginIdAsLong();
    }

}
